package com.loghead
{
	import flash.events.AsyncErrorEvent;
	import flash.events.StatusEvent;
	import flash.net.LocalConnection;
	import flash.utils.Dictionary;
	
	public class LogHead
	{
		//ID of the local connection used to interface with the Air viewer
		private static var _connectionID:String = "LogHead";
		
		//Local Connection object
		private static var _connection:LocalConnection;
		
		//Boolean that keeps track of whether LogHead is connected to the Air Viewer
		private static var _isConnected:Boolean;
		
		//A list of all the source classes that have been logged from
		private static var _logSources:Dictionary;
		
		private static var _detailedLoggingEnabled:Boolean = true;
		
		private static var _loggingEnabled:Boolean = true;
		
		private static var _echoTraces:Boolean = true;
		
		//Function names
		private static const LOG:String = "log";
		private static const LOG_DETAILED:String = "logDetailed";
		private static const CLEAR_LOG:String = "clear";
		
		private static var _pendingLogs:Array;
						
		public function LogHead()
		{
			
		}
		
		// loggingEnabled getter
		public static function get loggingEnabled():Boolean  { return _loggingEnabled; }
		
		// loggingEnabled setter
		public static function set loggingEnabled( newVal:Boolean ):void  { _loggingEnabled = newVal; }
		
		// detailedLoggingEnabled getter
		public static function get detailedLoggingEnabled():Boolean  { return _detailedLoggingEnabled; }
		
		// detailedLoggingEnabled setter
		public static function set detailedLoggingEnabled( newVal:Boolean ):void  { _detailedLoggingEnabled = newVal; }
		
		// echoTraces getter
		public static function get echoTraces():Boolean  { return _echoTraces; }
		
		// echoTraces setter
		public static function set echoTraces( newVal:Boolean ):void  { _echoTraces = newVal; }
		
		private static var _waitingForConnectionResponse:Boolean;
		
		/**
		 * Connects LogHead to the AIR viewer 
		 * @param connectionID
		 * 
		 */		
		public static function connect( connectionID:String = "" ):void
		{
			if( !_loggingEnabled ) return;
			
			//Set conection id
			if( connectionID != "" )
				_connectionID = connectionID;
			
			_logSources = new Dictionary();
			
			//Initialize Local Connection and connect with air app
			 _connection = new LocalConnection();
			 _connection.addEventListener(StatusEvent.STATUS, connectionStatusHandler );
			 _connection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, connectionErrorHandler );
			try
			{			
				//_connection.connect( _connectionID );
				
				trace("trying to connect to LogHead " + _connectionID );
				
				testConnection();
				
			}
			catch( e:ArgumentError )
			{
				_isConnected = false;
				trace("ERROR: LogHead was unable to connect becuase the connection name is already being used by another SWF " + e.message );
			}			
		}
		
		private static function testConnection():void
		{
			if( !_connection )
				connect();
			
			if( _waitingForConnectionResponse ) return;
				
			_connection.send( connectionString, LOG, "connected..", LogType.INFO.name );
			_waitingForConnectionResponse = true;
		}
		
		private static function connectionStatusHandler( e:StatusEvent ):void
		{
			//trace("Connection status " + e.level );
			 switch ( e.level ) {
                case "status":
                    //trace("LocalConnection.send() succeeded");
                    if( !_isConnected )
                    	connectionCompleteHandler();
                    break;
                case "error":
                   // trace("LocalConnection.send() failed");
                    break;
            }
		}
		
		private static function connectionCompleteHandler():void
		{			
			_isConnected = true;
			_waitingForConnectionResponse = false;
			processQue();
		}
		
		private static function processQue():void
		{
			var l:int = _pendingLogs.length;
			for ( var c:int = 0; c < l; c++ )
			{
				var lg:LogBase = _pendingLogs[c] as LogBase;
								
				sendLog( lg );
			}
		}
		
		private static function connectionErrorHandler( e:AsyncErrorEvent ):void
		{
			trace("error establishing connection " + e.text );
		}
		
		public static function clear():void
		{
			if( !_connection )
				connect();
			_connection.send( connectionString, CLEAR_LOG  );
		}
		
		public static function debug( message:String, color:int = -1 )
		{
			log( message, LogType.DEBUG );
		}
		
		public static function error( message:String, showStackTrace:Boolean = false, color:int = -1 )
		{
			log( message, LogType.ERROR );
		}
		
		public static function info( message:String, color = 0 ):void
		{
			log( message, LogType.INFO );
		}
		
		private static function get connectionString():String
		{
			return "app" + '#' + "LogHead" + ":" + _connectionID;
			//trace( "connection string " + "app" + '#' + "LogHead.3C462F51852DFE4A286D757AFE1D924C1FEBAD9A.1" + ":" + _connectionID );
			//return "app" + '#' + "LogHead.3C462F51852DFE4A286D757AFE1D924C1FEBAD9A.1" + ":" + _connectionID; 
		}
		
		private static function queLog( log:LogBase ):void
		{
			if( !_pendingLogs )
				_pendingLogs = new Array();
			
			_pendingLogs.push( log );
		}
		
		public static function log( msg:String, type:LogType ):void
		{
			if( !_loggingEnabled ) return;
			
			var remoteFunction:String;
						
			if( _detailedLoggingEnabled )
			{
				//TODO optimize this with regular expressions and add error handling
				
				remoteFunction = LOG_DETAILED;
				
				var breakMessage:String;
				
				var callClass:String = "";
				var callFuntion:String = "";
				var callingLine:String = "";
				//var callFile:String = "";
				
				//Intentionally throw an error so we can get the stack tract
				try { trace([].foo()) } catch( e:Error ){ breakMessage = e.getStackTrace() };
				
				//trace("break message " + breakMessage);
				//Grab lines
				var lines:Array = breakMessage.split( "\n" );
				
				//reg ex method of grabbing line # ended up taking longer than the manual way
				/*try 
				{ 
					lineNumber = breakMessage.match( new RegExp( "(\d{1,})($|(?=]))", "g" ) )[2]; 
				}catch( e:Error ){ };*/
				
				//trace("line number " + lineNumber  );
				try
				{
					
					//Grab source lines
					var sourceLine:String = lines[3];
					
					//trace("sourceLine " + sourceLine );
					
					//Get Line Number
					var tempNumSplit:Array = sourceLine.split(":");
					callingLine = tempNumSplit[tempNumSplit.length - 1];
					callingLine = callingLine.replace( "]", "" );
					//trace("lineNumber " + lineNumber );
					//*
					//Get Calling File
					//var pathArray:Array = String(tempNumSplit[tempNumSplit.length - 2]).split( "\\");
					//callFile = pathArray[pathArray.length - 1];
					
					//trace("callFile " + callFile );
					
					
					var packageLine:String = sourceLine.split( "[" )[0];
					
					packageLine = packageLine.replace( "at ", "" );
					
					var packageArray:Array = packageLine.split("/");
					
					//trace("package array " + packageArray + " length " + packageArray.length);
					callClass = packageArray[0];
					
					//Get the calling function and class															
					if( packageArray.length <= 1 )//caller is not a constructor
					{
											
						callFuntion = packageArray[0].split("::").pop();
						callClass = callClass.replace( "()", "" );							
					}
					else
						callFuntion = packageArray[packageArray.length - 1];
										
					//remove spaces, tabs, and $
					callClass = callClass.replace( new RegExp( " |\t|\$", "g" ), "" );
					
				}
				catch( e:Error )
				{
					trace("error grabbing log details");
				}
				
				if( _echoTraces )
					trace( callClass + " " + callFuntion + " " + callingLine + " " + msg );
				
				//_connection.send( connectionString, LOG_DETAILED, msg, type.name, callClass, callFuntion, lineNumber, _echoTraces  );
			}
			else
			{
				remoteFunction = LOG;
				
				//_connection.send( connectionString, LOG, msg, type.name, _echoTraces ); 
			}					
			
			var log:LogBase = new LogBase( msg, type, callClass, callFuntion, callingLine, type.color ); 
			
			if( _echoTraces )
				trace( _detailedLoggingEnabled ? log.detailedMessage : log.message );
					
			if( _isConnected )
			{
				sendLog( log );				
			}
			else
			{
				//que the log to be dispatched later
				queLog( log );
				
				connect();
			}	
		}
		
		private static function sendLog( log:LogBase ):void
		{
			var args:Array = new Array();
			var func:String;
			
			if( detailedLoggingEnabled ) //Build detailed log arguments
			{
				args = [ log.message, log.type.name, log.callingClass, log.callingFunction, log.callingLine ];
				
				func = LOG_DETAILED;
			}
			else//Build regular log arguments
			{
				func = LOG;
				args = [ log.message, log.type.name ];
			}
			
			//Make remote call	
			callRemoteFunction( func, args );
		}
				
		
		private static function callRemoteFunction( remoteFunction:String, args:Array ):void 
		{
			_connection.allowDomain('*');
			_connection.allowInsecureDomain('*'); 
			
			if( !args )
				args = new Array();
				
			args.unshift( connectionString, remoteFunction );
				
			//_connection.send( connectionString, LOG, msg, type.name, _echoTraces );	
			_connection.send.apply( null, args );			
		}
	}	
}

import com.loghead.LogType;
	
class PendingLog
{
	public var message:String;
	
	public var type:LogType;
	
	public var callFunction:String;
	
	public function PendingLog( msg:String, logType:LogType, callFunc:String ):void
	{
		message = msg;
		type = logType;
		callFunction = callFunc;
	}
}